#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tree.h"
#include "asm.h"

extern FILE * yyout;
char *Table="    ";
char AutoRegSelect = 0;
Num8 XmmSelect = 0;
char *GetOpName(OpCode opc){
    switch(opc){
        case AS_MOV: return "mov";
        case AS_LEA: return "lea";
        case AS_ADD: return "add";
        case AS_SUB: return "sub";
        case AS_MUL: return "mul";
        case AS_DIV: return "div";
        case AS_PUSH: return "push";
        case AS_POP: return "pop";
        case AS_FLD: return "fld";
        case AS_FSTP: return "fstp";
    }
}
char *GetMemSize(MemSize size){
    switch(size){
        case MEM_BYTE: return "BYTE ";
        case MEM_WORD: return "WORD ";
        case MEM_DWORD: return "DWORD ";
        case MEM_QWORD: return "QWORD ";
    }
}
char *GetRegName(Register reg){
    switch(reg){
        case R_AL: return "al";
        case R_AX: return "ax";
        case R_EAX: return "eax";
        case R_RAX: return "rax";
        case R_BL: return "bl";
        case R_BX: return "bx";
        case R_EBX: return "ebx";
        case R_RBX: return "rbx";
        case R_CL: return "cl";
        case R_CX: return "cx";
        case R_ECX: return "ecx";
        case R_RCX: return "rcx";
        case R_DL: return "dl";
        case R_DX: return "dx";
        case R_EDX: return "edx";
        case R_RDX: return "rdx";
        case R_SI: return "si";
        case R_ESI: return "esi";
        case R_RSI: return "rsi";
        case R_DI: return "di";
        case R_EDI: return "edi";
        case R_RDI: return "rdi";
        case R_SP: return "sp";
        case R_ESP: return "esp";
        case R_RSP: return "rsp";
        case R_BP: return "bp";
        case R_EBP: return "ebp";
        case R_RBP: return "rbp";
        case R_XMM: return "xmm";
        case R_R8B: return "r8b";
        case R_R8W: return "r8w";
        case R_R8D: return "r8d";
        case R_R8: return "r8";
        case R_R9B: return "r9b";
        case R_R9W: return "r9w";
        case R_R9D: return "r9d";
        case R_R9: return "r9";
    }
}

char isNoSign(DataType t){
    if(t==DATA_TYPE_STRING) return 1;
    return 0;
}
char isReg8(Register r){
    if(r==R_AL|| r==R_BL|| r==R_CL|| r==R_DL
    || r==R_R8B|| r==R_R9B) return 1;
    return 0;
}
char isReg16(Register r){
    if(r==R_AX||r==R_BX|| r==R_CX|| r==R_DX
    || r==R_SI|| r==R_DI|| r==R_SP|| r==R_BP
    || r==R_R8W|| r==R_R9W) return 1;
    return 0;
}
char isReg32(Register r){
    if(r==R_EAX|| r==R_EBX|| r==R_ECX|| r==R_EDX
    || r==R_ESI|| r==R_EDI|| r==R_ESP|| r==R_EBP
    || r==R_R8D|| r==R_R9D)  return 1;
    return 0;
}
char isReg64(Register r){
    if(r==R_RAX|| r==R_RBX|| r==R_RCX|| r==R_RDX
    || r==R_RSI|| r==R_RDI|| r==R_RSP|| r==R_RBP
    || r==R_R8 || r==R_R9 )  return 1;
    return 0;
}
char GeiBitSize(Register r){
    if(isReg8(r)) return 8;
    else if(isReg16(r)) return 16;
    else if(isReg32(r)) return 32;
    else if(isReg64(r)) return 64;
}
char *GetObj(_OBJ_ *obj, GetObjExtendOPT e_opt){
    if(!obj)  return " ";

    if(obj->type == OBJ_REG){
        char *size=GetMemSize(obj->Reg.size);
        char *name=GetRegName(obj->Reg.name);

        static char ret[8];
        if(obj->Reg.name == R_XMM){
            sprintf(ret, "%s%lld", name, obj->Imm.n);
            return ret;
        }

        if(size){
            sprintf(ret, "%s%s", size, name);
            return ret;
        }
        return name;
    }else if(obj->type == OBJ_IMM){
        static char num[8];
        sprintf(num, "%lld", obj->Imm.n); 
        return num;
    }else if(obj->type == OBJ_MEM){
        char *size=GetMemSize(obj->Reg.size);
        char *name=GetRegName(obj->Reg.name);
        static char ret[10];
        if(e_opt == EP_NOMemSize)
            sprintf(ret, "[%s-%lld]", name, obj->Imm.n);
        else
            sprintf(ret, "%s[%s-%lld]",size, name, obj->Imm.n);

        return ret;
    }else if(obj->type == OBJ_STR){
        return obj->Str.name;
    }
}

void ProduceAsm(Command *cmd){
    if(!cmd->source){
        // cmd target
        fprintf(yyout, "%s%s %s\n", Table, GetOpName(cmd->op), GetObj(cmd->target,EP_NULL));
    }else
    {
        //cmd target, source
        fprintf(yyout, "%s%s %s, %s\n", Table, GetOpName(cmd->op), GetObj(cmd->target,EP_NULL), GetObj(cmd->source,EP_NULL) );
    }
    
    
}

char * ProduceFloatAsm(Num8 n, _OBJ_ *obj){
    static char info[8];
    if(obj->type == OBJ_STR){ //标号
        sprintf(info, "%s[%s%lld]", GetMemSize(obj->Str.size), obj->Str.name, n);
    }else{ //内存
        char *size=GetMemSize(obj->Reg.size);
        char *name=GetRegName(obj->Reg.name);
        sprintf(info, "%s[%lld+%s]", size,n,name);
    }
    return info;
}

void _mov(_OBJ_ *target, _OBJ_ *source){
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_MOV;
    cmd->target = target;
    cmd->source = source;
    ProduceAsm(cmd);
}
DataType _movsx(_OBJ_ *target, _OBJ_ *source){
    
    char t_size = GeiBitSize(target->Reg.name);
    char s_size = GeiBitSize(source->Reg.name);
    // printf("t_size: %d, s_size:%d \n", t_size, s_size);

    if(s_size>=32 && t_size<=s_size) return source->dt;
    
    if(t_size==8){
        target->Reg.name = (Register)(target->Reg.name+2);
        target->dt = (DataType)(target->dt+2);
    }else if(t_size==16){
        target->Reg.name = (Register)(target->Reg.name+1);
        target->dt = (DataType)(target->dt+1);
    }
    fprintf(yyout, "%smovsx %s, %s\n", Table, GetObj(target,EP_NULL), GetObj(source,EP_NULL) );
    return target->dt;
    
}
void _add(_OBJ_ *target, _OBJ_ *source){
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_ADD;
    cmd->target = target;
    cmd->source = source;
    ProduceAsm(cmd);
}
void _sub(_OBJ_ *target, _OBJ_ *source){
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_SUB;
    cmd->target = target;
    cmd->source = source;
    ProduceAsm(cmd);
}
void _imul(_OBJ_ *target, _OBJ_ *source){
    //检测source是否是纯数字, 如果不是需要mov到dx
    if(source->type == OBJ_IMM){
        _mov(dx(target->dt), source);
    }
    fprintf(yyout, "%simul %s\n", Table, GetObj( dx(target->dt), EP_NULL) );
    fprintf(yyout, "%smov DWORD [rsp-8], eax\n", Table);
    fprintf(yyout, "%smov DWORD [rsp-4], edx\n", Table);
    fprintf(yyout, "%smov rax, QWORD [rsp-8]\n", Table);    
}
void _idiv(_OBJ_ *target, _OBJ_ *source){
    //fprintf(yyout, "%scdq\n", Table);
    // CBW、CWD、CDQ 的作用: 低位符号位扩展到高位
    //al->ah, ax->dx, eax->edx
    //除了8位, 16 32 64都是(dx:ax|edx:eax|rdx:rax)为被除数

    if(source->type == OBJ_IMM){
        _mov(bx(target->dt), source);
    }else{
        _mov(bx(target->dt), dx(target->dt));
    }
    //整数除法最大64位,使用低位就行了
    //高位永远是0
    //只取商,不要余数
    _mov(dx(DATA_TYPE_NUMBER_8), imm(0));
    // 除数在bx或立即数
    fprintf(yyout, "%sidiv %s\n", Table, GetObj( bx(target->dt), EP_NULL) );
}

void _push(_OBJ_ *target){
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_PUSH;
    cmd->target = target;
    ProduceAsm(cmd);
}
void _pop(_OBJ_ *target){
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_POP;
    cmd->target = target;
    ProduceAsm(cmd);
}
void _cvtsd2(int t, _OBJ_ *target, _OBJ_ *source){
    char cmd[9];//结束符需要占1字节 \0
    if(t == 1) strcpy( cmd, "cvtsd2ss"); //双转单
    if(t == 2) strcpy( cmd, "cvtss2sd"); //单转双
    // cvtsi2sd xmm, reg64 64位整数转双
    // cvtsi2ss xmm, reg32 32位整数转单
    // cvtsd2si reg64, xmm, 双转64位整数
    // cvtss2si reg32, xmm, 单转32位整数

    XmmSelect++;
    fprintf(yyout, "%s%s %s, %s\n", Table, cmd , GetObj(target,EP_NULL), GetObj(source,EP_NOMemSize) );
    
}

void _fadd(DataType t, _OBJ_ *target){ 
    char cmd[6]; //结束符需要占1字节 \0
    if(t == DATA_TYPE_F_NUMBER_4) strcpy( cmd, "addss");
    if(t == DATA_TYPE_F_NUMBER_8) strcpy( cmd, "addsd");
    //内存取值
    if(target){
        fprintf(yyout, "%s%s xmm%lld, %s\n", Table, cmd, XmmSelect-1,  GetObj(target,EP_NULL) );
    }else{ //寄存器取值
        XmmSelect--;
        fprintf(yyout, "%s%s xmm%lld, xmm%lld\n", Table, cmd, XmmSelect-1,  XmmSelect);
    }
} 
void _lea(_OBJ_ *target, _OBJ_ *source){ 
    Command *cmd = (Command *)malloc(sizeof(Command));
    cmd->op = AS_LEA;
    cmd->target = target;
    cmd->source = source;
    ProduceAsm(cmd);
} 
Assembly *GetCodeGenerator(){
    Assembly *gen = (Assembly *)malloc(sizeof(Assembly));
    gen->mov=_mov;
    gen->lea=_lea;
    gen->cvtsd2=_cvtsd2;
    gen->fadd=_fadd;
    gen->add=_add;
    gen->sub=_sub;
    gen->imul=_imul;
    gen->idiv=_idiv;
    gen->movsx=_movsx;
    gen->push=_push;
    gen->pop=_pop;
    return gen;
}
Register SetReg(DataType t, Register base){
    switch(t){
        case DATA_TYPE_NUMBER_1: return base;
        case DATA_TYPE_NUMBER_2: return (Register)(base+1);
        case DATA_TYPE_NUMBER_4: return (Register)(base+2);
        case DATA_TYPE_NUMBER_8: return (Register)(base+3);
        case DATA_TYPE_STRING: return (Register)(base+3);
    }
}
MemSize SetAsmSize(DataType t){
    if(t==DATA_TYPE_NUMBER_1){
        return MEM_BYTE;
    }else if(t==DATA_TYPE_NUMBER_2){
        return MEM_WORD;
    }else if(t==DATA_TYPE_NUMBER_4||t==DATA_TYPE_F_NUMBER_4){
        return MEM_DWORD;
    }else if(t==DATA_TYPE_NUMBER_8||t==DATA_TYPE_F_NUMBER_8){
        return MEM_QWORD;
    }
}

_OBJ_ *imm(Num8 n){
    _OBJ_ * obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_IMM;
    obj->Imm.n = n;
    return obj;
}
_OBJ_ *mem(Num8 n, _OBJ_ *obj){
    obj->type = OBJ_MEM;
    obj->Imm.n = n;
    return obj;
}
_OBJ_ *ax(DataType t){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_REG;
    obj->Reg.name = SetReg(t, R_AL);
    obj->dt = t;
    return obj;
}
_OBJ_ *bx(DataType t){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_REG;
    obj->Reg.name = SetReg(t, R_BL);
    obj->dt = t;
    return obj;
}
_OBJ_ *dx(DataType t){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_REG;
    obj->Reg.name = SetReg(t, R_DL);
    obj->dt = t;
    return obj;
}
_OBJ_ *xmm(){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_REG;
    obj->Imm.n = XmmSelect;
    obj->Reg.name = R_XMM;
    return obj;
}

_OBJ_ *memrel(Num4 no){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    static char str[100];
    sprintf(str, "[rel FLT%ld]", no);
    obj->type = OBJ_STR;
    obj->Str.name = str;
    return obj;
}

_OBJ_ *bp(DataType t){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_REG;
    obj->Reg.size = SetAsmSize(t);
    obj->Reg.name = R_RBP;
    obj->dt = t;
    return obj;
}
_OBJ_ *AutoReg(DataType t){
    //程序逻辑
    if(AutoRegSelect){
        return bx(t);
    }else{
        AutoRegSelect = 1;
        return ax(t);
    }
}
_OBJ_ *Floating(DataType t){
    _OBJ_ *obj = (_OBJ_ *)malloc(sizeof(_OBJ_));
    obj->type = OBJ_STR;
    obj->Str.size = SetAsmSize(t);
    obj->Str.name = "FLT";
    obj->dt = t;
    return obj;
}

void PushParams(DataType t, Register s_r, Register t_r){
    if( isReg8(s_r) || isReg16(s_r) ){
        if(isNoSign(t))
            fprintf(yyout, "%smovzx %s, %s\n", Table, GetRegName(t_r), GetRegName(s_r));
        else
            fprintf(yyout, "%smovsx %s, %s\n", Table, GetRegName(t_r), GetRegName(s_r));
    }else if( isReg32(s_r) ){
        fprintf(yyout, "%smov %s,%s\n", Table, GetRegName(t_r), GetRegName(s_r));
    }else{
        fprintf(yyout, "%smov %s,%s\n", Table, GetRegName((Register)(t_r+1)), GetRegName(s_r));
    }
}
_OBJ_ *StackReg(Num no, DataType t){
    //值在ax里面
    Register _reg = SetReg(t, R_AL);
    switch (no)
    {
        //di
        case 1: PushParams(t, _reg, R_EDI); break;
        //si
        case 2: PushParams(t, _reg, R_ESI); break;
        //dx
        case 3: PushParams(t, _reg, R_EDX); break;
        //cx
        case 4: PushParams(t, _reg, R_ECX); break;
        //r8
        case 5: PushParams(t, _reg, R_R8D); break;
        //r9
        case 6: PushParams(t, _reg, R_R9D); break;
        // push 64
        default:
            fprintf(yyout, "%spush rax\n", Table);
        break;
    }
}

_OBJ_ *StackXmm(Num no, DataType t){
    //TODO
    //如果是可变参数需要把float类型提升到double
    //可以 cvtss2sd xmm0, xmm0
    /*
    movsx esi, BYTE [rbp-15]
    movsx edx, WORD [rbp-14]
    mov ecx, [rbp-12]
    mov r8,  [rbp-8]
    lea rdi, [rel _str]
    call out
    */
    if(no<8){
        if(no!=0) fprintf(yyout, "%smovapd xmm%d, xmm0\n", Table, no);
    }else{// push mem
        fprintf(yyout, "%smovq rax, xmm0\n", Table);
        fprintf(yyout, "%spush rax\n", Table);
    }
}